<!-- 绘图 -->
<template>
	<!-- 百分比的额尺寸模式时用到的参考标准 -->
	<div v-if="loading === -1" id="div" style="width: 100%;height: 100%;"/>
	<!-- 进入绘图之后 -->
	<div v-else :style="[imageStyle, hideStyle, tempFilePath && showImage ? {backgroundImage: `url(${tempFilePath})`, backgroundSize: 'cover'} : {}]" @click.stop="e => !disabled && tempFilePath && showImage && core.common.previewImage(tempFilePath)">
		<canvas v-if="!(tempFilePath && showImage)" type="2d" :id="canvasId" :canvas-id="canvasId" :width="iWidth" :height="iHeight" :style="[imageStyle]"/>
	</div>
</template>

<script>
	
	import base from '@/components/index.vue'
	
	/**
	 * uniapp官方文档：https://uniapp.dcloud.net.cn/api/canvas/CanvasContext.html
	 * 微信小程序官方文档：
	 * https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html
	 * https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html
	 * https://developers.weixin.qq.com/miniprogram/dev/api/canvas/Canvas.html
	 * 
	 * 传入画布的宽高。画布一般小于屏幕宽度，也可以传值大于屏幕宽度，不传默认是屏幕宽度
	 * 导出的图片的宽高是画布宽高乘以分辨率
	 */
	
	export default {
		extends: base,
		emits: ['success'],
		props: {
			// 画布的宽度。默认屏幕宽。建议必传
			width: [String, Number],
			// 画布的高度。默认宽高比9:4。建议必传
			height: [String, Number],
			// 绘图的具体方法
			drawAction: {
				type: Function,
				default: (context, size, drawImage, complete) => {
					/**
					 * context：绘图对象
					 * size：方法名。所有的尺寸按照一样的规则缩放。比如：size(180)
					 * drawImage：绘制图片。只需要原始src即可
					 * getTempImageData：绘制完毕后的回调，以生成新图，但要求showImage支持
					 * canvasToTempFilePath：生成图片的参数
					 * getTempImageUrl：生成新图的方法。需传入得到的绘图结果
					 * this：当前组件对象。自动以绘图输出需要用到
					 */
				}
			},
			// 是否隐藏绘制过程。隐藏绘制过程，表示在看不见的远处绘制。不隐藏所见即是绘制的结果
			hideCanvasing: Boolean,
			// 是否显示绘制的图
			showImage: Boolean,
			// 是否禁止预览
			disabled: Boolean,
			// 是否显示loading框
			showLoading: Boolean
		},
		data() {
			return {
				/**
				 * 图片加载状态
				 * -2：完全不显示
				 * -1：初始状态，还未开始加载
				 * 0：正常绘制完毕
				 * 1：正在绘制中。如果再父组件调用中执行了绘图结束，这里的loading就不会变为0或者2
				 * 2：绘制失败
				 */
				loading: -1,
				// 实际画布的宽度
				iWidth: this.width,
				// 实际画布的高度
				iHeight: this.height,
				// 绘制后的图
				tempFilePath: '',
			}
		},
		computed: {
			canvasId() {
				return this.randomString()
			},
			initialSize() {
				return this.core.common.initialSize
			},
			imageStyle() { // 图片或者画布的尺寸
				return {
					width: this.iWidth + 'px',
					height: this.iHeight + 'px'
				}
			},
			hideStyle() {
				return this.hideCanvasing ? {position: 'absolute', top: 0, left: '-10000px'} : {}
			},
			pixelRatio() {
				return this.core.store.device.info.devicePixelRatio
			}
		},
		mounted() {
			// #ifdef MP-WEIXIN
			this.core.delayAction(() => {
				this.initial()
			}, 300)
			// #endif
			// #ifndef MP-WEIXIN
			this.$nextTick(() => {
				this.initial()
			})
			// #endif
		},
		methods: {
			// 初始化方法
			initial() {
				var {width = this.$root.RealWidth, height, pixelRatio} = this
				width = this.initialSize(width, 'width', '#div', this)
				height = this.initialSize(height, 'height', '#div', this)
				if (!height) {height = width * 4 / 9}
				this.iWidth = width
				this.iHeight = height
				this.$nextTick(() => {
					this.draw()
				})
			},
			
			/**
			 * 生成随机字符串
			 * length：长度
			 * source：字符串来源。数字放在最后十位
			 */
			randomString(length = 16, source = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789') {
				let string = ''
				for (let i = 0; i < length; i++) {
					// 第一位不为数字
					const random = Math.floor(Math.random() * (i === 0 ? source.length - 10 : source.length))
					string += source.substr(random, 1)
				}
				return string
			},
			
			// 所有的尺寸都要按照这个函数进行转换
			size(x) {
				return x * this.pixelRatio // 直接px
			},
			
			/**
			 * 绘图的方法
			 * 
			 * 官方文档：https://uniapp.dcloud.net.cn/api/canvas/CanvasContext.html#
			 * 小程序绘图参考：https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html
			 */
			draw() {
				this.loading = 1
				const {iWidth, iHeight, drawAction, pixelRatio, canvasId, size, showImage, showLoading} = this
				if (showLoading) {
					uni.showLoading({
						title: '图片生成中'
					})
				}
				/**
				 * 图片绘制方法。父组件直接调用该方法即可
				 * 微信小程序与其它不一样。微信小程序的wh还必须要有值
				 * 小程序和App没有跨域问题
				 * H5会有跨域问题！
				 * 
				 * x,y,w,h都是相对于画布的位置，而非原始图片的位置
				 */
				const drawImage = (src, x = 0, y = 0, w = iWidth, h = iHeight, context, canvas) => {
					return new Promise((resolve, reject) => {
						// 给地址加一个时间戳，据说可以避免跨域
						if (typeof(src) === 'string' && src.substr(0, 4) === 'http') {
							src += `?t=${Date.now()}`
						}
						if (canvas) {
							const image = canvas.createImage()
							image.onload = () => {
							    context.drawImage(image, x, y, w, h)
							    resolve()
							}
							image.onerror = (err) => {
								if (showLoading) {
									uni.hideLoading()
									this.core.showToast('绘制失败')
								}
								reject(err)
							}
							image.src = src
						} else {
							context.drawImage(src, x, y, w, h)
							resolve()
						}
					})
				}
				/**
				 * 生成图片的参数。会传递给父组件，可进行自定义图片输出，输出方法参考getTempImageData
				 * 父组件中要重写，请参考里面的写法。尤其是width、height和destWidth、destHeight
				 * 微信小程序主要传canvas；其它传canvasId
				 * 
				 * 只有在导出的时候需要用到size
				 */
				const canvasToTempFilePath = {
					canvasId,
					useCORS: true, x: 0, y: 0, // 画布起点
					width: iWidth, height: iHeight, // 画布的宽高
					destWidth: size(iWidth), destHeight: size(iHeight), // 输出图片的宽高
					fileType: 'png', // 目标文件的类型，只支持 'jpg' 或 'png'。默认为 'png'
					quality: 1.0, // 图片的质量，取值范围为 (0, 1]，不在范围内时当作1.0处理
				}
				
				// 通过绘图结果得到图片
				const getTempImageUrl = (res) => {
					// #ifdef MP-WEIXIN
					// 生成的图片临时文件路径
					return res.tempFilePath
					// #endif
					// #ifndef MP-WEIXIN
					// 以下三种方式都可以
					
					// 方法一：直接返回base64。数据量大
					// this.$emit('success', res.tempFilePath)
					// return
					
					// 方法二：自己实现base64转本地文件
					const base64ToBlob = (base64Data) => {
						let arr = base64Data.split(','),
							fileType = arr[0].match(/:(.*?);/)[1],
							bstr = atob(arr[1]),
							l = bstr.length,
							u8Arr = new Uint8Array(l);
							while (l--) {
								u8Arr[l] = bstr.charCodeAt(l);
							}
							return new Blob([u8Arr], {
								type: fileType
							})
					}
					const imgUrl = URL ? URL.createObjectURL(base64ToBlob(res.tempFilePath)) : res.tempFilePath
					return imgUrl
					// #endif
				}
				
				/**
				 * 输出图片。父组件如果需自定义输出，在drawAction中要参照该写法
				 * 微信小程序直接执行，其它需context通过draw(true, action)来执行
				 */
				const getTempImageData = (context) => {
					const action = () => {
						uni.canvasToTempFilePath({
						    ...canvasToTempFilePath,
						    success: res => {
								const imgUrl = getTempImageUrl(res)
								this.$emit('success', imgUrl)
								this.tempFilePath = imgUrl
								if (showLoading) {
									uni.hideLoading()
								}
								this.loading = 0
						    },
							fail: (err) => {
								console.log(err)
								if (showLoading) {
									uni.hideLoading()
								}
								this.loading = 2
							}
						})
					}
					if (showImage) {
						// #ifdef MP-WEIXIN
						action()
						// #endif
						// #ifndef MP-WEIXIN
						context.draw(true, action)
						// #endif
					}
				}
				
				// #ifdef MP-WEIXIN
				// 非页面文件，必须用in(this)
				this.core.delayAction(() => {
					const query = uni.createSelectorQuery().in(this)
					query.select(`#${canvasId}`).fields({node: true, size: true}).exec(res => {
						// https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html
						if (res[0]) {
							// Canvas对象和画布的实际绘制宽高
							const canvas = res[0].node
							canvasToTempFilePath.canvas = canvas
							// 渲染上下文
							const context = canvas.getContext('2d')
							const {width, height} = res[0]
							// 初始化画布大小。微信小程序的画布大小要放大
							// 初始化画布大小
							const dpr = wx.getWindowInfo().pixelRatio
							canvas.width = width * dpr
							canvas.height = height * dpr
							context.scale(dpr, dpr)
							// 清空画布
							context.clearRect(0, 0, width, height)
							/**
							 * 绘制动作
							 * 小程序要用this.$parent否则drawAction中没有this对象
							 */
							var index = 0, parent = this.$parent, drawAction = null
							// 最多往上十次。一般就一次，父组件中有外包的组件则多几次
							for (let i = 0; i < 10; i++) {
								if (parent.drawAction) {
									drawAction = parent.drawAction
									break
								}
								parent = parent.$parent
							}
							if (drawAction) {
								drawAction(context, size, (src, x, y, w, h) => {
									return drawImage(src, x, y, w, h, context, canvas)
								}, getTempImageData, canvasToTempFilePath, getTempImageUrl, this)
							} else {
								getTempImageData()
							}
						}
					})
				}, 100)
				// #endif
				// #ifndef MP-WEIXIN
				const context = uni.createCanvasContext(canvasId, this)
				canvasToTempFilePath.canvasId = canvasId
				// 清空画布
				context.clearRect(0, 0, iWidth, iHeight)
				/**
				 * 绘制动作
				 * 至少h5的drawAction正常，小程序异常（方法内部无法获取父组件的this）
				 * H5有跨域问题，给图片添加一个时间戳。有可能避免跨域，实际似乎也无效
				 */
				if (drawAction) {
					drawAction(context, size, (src, x, y, w, h) => {
						return drawImage(src, x, y, w, h, context)
					}, () => {
						getTempImageData(context)
					}, canvasToTempFilePath, getTempImageUrl, this)
				} else {
					getTempImageData(context)
				}
				// #endif
			},
		}
	}
</script>

<style>
</style>